/*
 * @(#) $Header$
 *
 * Copyright (C) 2007  Forklabs Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.media.jai.operator;

import java.awt.RenderingHints;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderableImage;
import javax.media.jai.JAI;
import javax.media.jai.OperationDescriptor;
import javax.media.jai.OperationDescriptorImpl;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.RenderableOp;
import javax.media.jai.RenderedOp;
import javax.media.jai.registry.RenderableRegistryMode;
import javax.media.jai.registry.RenderedRegistryMode;

/**
 * Class {@code AutoRescaleDescriptor} is an {@link OperationDescriptor}
 * describing the <em>autorescale</em> operation. This operation is a
 * simplification of the <em>rescale</em> operator such that the calculation of
 * the offsets and the constants are made from a range of values.
 * <p>
 * The <em>autorescale</em> operation takes two parameters, the lower bounds and
 * the upper bands of the range.
 * <p>
 * If a band of the image is constant, that is all the values are the same, the
 * new values of said band will be <em>(upperbounds - lowerbounds) / 2</em>.
 *
 * <table border=1>
 * <caption>Resource List</caption>
 * <tr><th>Name</th>        <th>Value</th></tr>
 * <tr><td>GlobalName</td>  <td>AutoRescale</td></tr>
 * <tr><td>LocalName</td>   <td>AutoRescale</td></tr>
 * <tr><td>Vendor</td>      <td>ca.forklabs.media.jai.opimage</td></tr>
 * <tr><td>Description</td> <td>Rescale made easier</td></tr>
 * <tr><td>DocURL</td>      <td>n/a</td></tr>
 * <tr><td>Version</td>     <td>$Version$</td></tr>
 * <tr><td>Arg0Desct</td>   <td>The lower bounds of the range</td></tr>
 * <tr><td>Arg1Desct</td>   <td>The upper bounds of the range</td></tr>
 * </table>
 *
 * <table border=1>
 * <caption>Parameter List</caption>
 * <tr><th>Name</th>         <th>Class Type</th>   <th>Default Value</th></tr>
 * <tr><td>lowerbounds</td>   <td>double[]</td>       <td>new double[] { 0.0, }</td>
 * <tr><td>upperbounds</td>   <td>double[]</td>       <td>new double[] { 255.0, }</td>
 * </table>
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.media.jai.operator.AutoRescaleDescriptor">Daniel Léonard</a>
 * @version $Revision$
 */
public class AutoRescaleDescriptor extends OperationDescriptorImpl {

//---------------------------
// Class variables
//---------------------------

   /** <em>serialVersionUID</em>. */
   private static final long serialVersionUID = 690373081910947008L;

   /** The name of this operator. */
   @SuppressWarnings("nls")
   public static final String NAME = "AutoRescale";

   /** The name of the lower bound parameter. */
   @SuppressWarnings("nls")
   public static final String LOWER_BOUND_PARAMETER_NAME = "lowerbounds";
   /** The name of the upper bound parameter. */
   @SuppressWarnings("nls")
   public static final String UPPER_BOUND_PARAMETER_NAME = "upperbounds";

   /** The index in the parameter block of the lower bound parameter. */
   public static final int LOWER_BOUND_PARAMETER_INDEX = 0;
   /** The index in the parameter block of the upper bound parameter. */
   public static final int UPPER_BOUND_PARAMETER_INDEX = 1;

   /** The lower bound default value. */
   public static final double LOWER_BOUND_DEFAULT_VALUE = 0.0;
   /** The upper bound default value. */
   public static final double UPPER_BOUND_DEFAULT_VALUE = 255.0;


   /**
    * The resource strings that provide the general documentation and specify
    * the parameter list for this operation.
    */
   @SuppressWarnings("nls")
   private static final String[][] RESOURCES =
   {
      { "GlobalName",  AutoRescaleDescriptor.NAME, },
      { "LocalName",   AutoRescaleDescriptor.NAME, },
      { "Vendor",      "ca.forklabs.media.jai.opimage", },
      { "Description", AutoRescaleDescriptor.getDescription(), },
      { "DocURL",      "n/a", },
      { "Version",     "$Version$", },
      { "arg0Desc",    AutoRescaleDescriptor.getArg0Description(), },
      { "arg1Desc",    AutoRescaleDescriptor.getArg1Description(), },
   };

   /** The supported modes. */
   private static final String[] SUPPORTED_MODES =
   {
      RenderedRegistryMode.MODE_NAME,
      RenderableRegistryMode.MODE_NAME,
   };

   /** The parameter class list for this operation. */
   private static final Class<?>[] PARAMETER_CLASSES =
   {
       double[].class, double[].class,
   };

   /** The parameter name list for this operation. */
   private static final String[] PARAMETER_NAMES =
   {
      AutoRescaleDescriptor.LOWER_BOUND_PARAMETER_NAME,
      AutoRescaleDescriptor.UPPER_BOUND_PARAMETER_NAME,
   };

   /** The default parameters. */
   private static final Object[] PARAMETER_DEFAULTS = new Object[]
   {
      new double[] { LOWER_BOUND_DEFAULT_VALUE, },
      new double[] { UPPER_BOUND_DEFAULT_VALUE, },
   };

   /** Valid parameter values. */
   private static final Object[] VALID_PARAMETERS = new Object[]
   {
      null,
      null,
   };


//---------------------------
// Constructor
//---------------------------

   /**
    * Constructor.
    */
   public AutoRescaleDescriptor() {
      super(RESOURCES, SUPPORTED_MODES, 1, PARAMETER_NAMES, PARAMETER_CLASSES, PARAMETER_DEFAULTS, VALID_PARAMETERS);
      }


//---------------------------
// Overridden methods from javax.media.jai.OperationDescriptorImpl
//---------------------------

   /**
    * Validates that the lower bound is smaller than or equal to the upper
    * bound.
    * @param   mode   the rendering mode.
    * @param   pb   the parameter block.
    * @param   sb   string buffer for error messages.
    * @return   {@code true} if all is well, {@code false} otherwise.
    */
   @Override
   protected boolean validateParameters(String mode, ParameterBlock pb, StringBuffer sb) {
      boolean is_ok = super.validateParameters(mode, pb, sb);
      if (false == is_ok) {
         return is_ok;
         }

      double[] lower_bounds = (double[]) pb.getObjectParameter(LOWER_BOUND_PARAMETER_INDEX);
      double[] upper_bounds = (double[]) pb.getObjectParameter(UPPER_BOUND_PARAMETER_INDEX);
      int lb_len = lower_bounds.length;
      int up_len = upper_bounds.length;

      is_ok = (1 <= lb_len);
      if (false == is_ok) {
         String message = this.getNoLowerBounds();
         sb.append(message);
         return is_ok;
         }

      is_ok = (1 <= up_len);
      if (false == is_ok) {
         String message = this.getNoUpperBounds();
         sb.append(message);
         return is_ok;
         }

      is_ok = (lb_len == up_len);
      if (false == is_ok) {
         String message = this.getNotSameNumberOfBoundsErrorMessage(lower_bounds.length, upper_bounds.length);
         sb.append(message);
         return is_ok;
         }

      for (int i = 0, len = lb_len; i < len; i++) {
         double lower_bound = lower_bounds[i];
         double upper_bound = upper_bounds[i];

         is_ok = (lower_bound < upper_bound);
         if (false == is_ok) {
            String message = this.getLowerBoundGreaterThanUpperBoundErrorMessage(lower_bound, upper_bound);
            sb.append(message);
            return is_ok;
            }
         }

      return is_ok;
      }


//---------------------------
// Class methods
//---------------------------

   /**
    * Creates and fills a new parameter block.
    * @param   mode   the rendering mode.
    * @param   source   the source image.
    * @param   lower_bounds   the lower bound.
    * @param   upper_bounds   the upper bound.
    * @return   a new parameter block.
    */
   protected static ParameterBlockJAI createParameterBlock(String mode, Object source, double[] lower_bounds, double[] upper_bounds) {
      String name = AutoRescaleDescriptor.NAME;
      ParameterBlockJAI pb = new ParameterBlockJAI(name, mode);
      if (null != source) {
         pb.addSource(source);
         }
      pb.setParameter(AutoRescaleDescriptor.LOWER_BOUND_PARAMETER_NAME, lower_bounds);
      pb.setParameter(AutoRescaleDescriptor.UPPER_BOUND_PARAMETER_NAME, upper_bounds);
      return pb;
      }

   /**
    * Creates and fills a new parameter block for the rendered mode.
    * @param   source   the source image.
    * @param   lower_bounds   the lower bound.
    * @param   upper_bounds   the upper bound.
    * @return   a new parameter block.
    */
   public static ParameterBlockJAI createParameterBlock(RenderedImage source, double[] lower_bounds, double[] upper_bounds) {
      String mode = RenderedRegistryMode.MODE_NAME;
      ParameterBlockJAI pb_jai = AutoRescaleDescriptor.createParameterBlock(mode, source, lower_bounds, upper_bounds);
      return pb_jai;
      }

   /**
    * Creates and fills a new parameter block for the renderable mode.
    * @param   source   the source image.
    * @param   lower_bounds   the lower bound.
    * @param   upper_bounds   the upper bound.
    * @return   a new parameter block.
    */
   public static ParameterBlockJAI createParameterBlock(RenderableImage source, double[] lower_bounds, double[] upper_bounds) {
      String mode = RenderableRegistryMode.MODE_NAME;
      ParameterBlockJAI pb_jai = AutoRescaleDescriptor.createParameterBlock(mode, source, lower_bounds, upper_bounds);
      return pb_jai;
      }

   /**
    * Performs the <em>autorescale</em> operation on a rendered image.
    * @param   source   the image to enhance.
    * @param   lower_bounds   the lower bound.
    * @param   upper_bounds   the upper bound.
    * @param   hints    the rendering hints, may be {@code null}.
    * @return   the rendered result image.
    */
   public static RenderedOp create(RenderedImage source, double[] lower_bounds, double[] upper_bounds, RenderingHints hints) {
      String operation = AutoRescaleDescriptor.NAME;
      ParameterBlockJAI parameter_block = AutoRescaleDescriptor.createParameterBlock(source, lower_bounds, upper_bounds);
      RenderedOp image = JAI.create(operation, parameter_block, hints);
      return image;
      }

   /**
    * Performs the <em>autorescale</em> operation on a renderable image.
    * @param   source   the image to enhance.
    * @param   lower_bounds   the lower bound.
    * @param   upper_bounds   the upper bound.
    * @param   hints    the rendering hints, may be {@code null}.
    * @return   the rendered result image.
    */
   public static RenderableOp createRenderable(RenderableImage source, double[] lower_bounds, double[] upper_bounds, RenderingHints hints) {
      String operation = AutoRescaleDescriptor.NAME;
      ParameterBlockJAI parameter_block = AutoRescaleDescriptor.createParameterBlock(source, lower_bounds, upper_bounds);
      RenderableOp image = JAI.createRenderable(operation, parameter_block, hints);
      return image;
      }


//---------------------------
// External resources methods
//---------------------------

   /**
    * Gets the description of this operation.
    * @return   the description message.
    */
   protected static String getDescription() {
      String key = Resources.AUTO_RESCALE_DESCRIPTION;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   /**
    * Gets the description for the first argument, the lower bounds.
    * @return   the description message.
    */
   protected static String getArg0Description() {
      String key = Resources.AUTO_RESCALE_ARG0_DESCRIPTION;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   /**
    * Gets the description for the second argument, the upper bounds.
    * @return   the description message.
    */
   protected static String getArg1Description() {
      String key = Resources.AUTO_RESCALE_ARG1_DESCRIPTION;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   /**
    * Gets the error message saying that there are no lower bounds.
    * @return   the error message.
    */
   protected String getNoLowerBounds() {
      String key = Resources.AUTO_RESCALE_NO_LOWER_BOUNDS;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   /**
    * Gets the error message saying that there are no upper bounds.
    * @return   the error message.
    */
   protected String getNoUpperBounds() {
      String key = Resources.AUTO_RESCALE_NO_UPPER_BOUNDS;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   /**
    * Gets the error message saying a there are not the same number of lower
    * bound and upper bounds.
    * @param   lower_bound_len   the lower bounds.
    * @param   upper_bound_len   the upper bounds.
    * @return   the description message.
    */
   @SuppressWarnings("boxing")
   protected String getNotSameNumberOfBoundsErrorMessage(int lower_bound_len, int upper_bound_len) {
      String key = Resources.AUTO_RESCALE_NOT_SAME_NUMBER_OF_BOUNDS;
      String message = Resources.getLocalizedString(key, lower_bound_len, upper_bound_len);
      return message;
      }

   /**
    * Gets the error message saying a lower bound is greater than its upper bound.
    * @param   lower_bound   the lower bound.
    * @param   upper_bound   the upper bound.
    * @return   the description message.
    */
   @SuppressWarnings("boxing")
   protected String getLowerBoundGreaterThanUpperBoundErrorMessage(double lower_bound, double upper_bound) {
      String key = Resources.AUTO_RESCALE_LOWER_GREATER_THAN_UPPER;
      String message = Resources.getLocalizedString(key, lower_bound, upper_bound);
      return message;
      }

   }
